package net.entelijan.cobean.bind.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.swing.JFrame;

import net.entelijan.cobean.bind.IModelChangeListener;

import org.aopalliance.aop.Advice;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AbstractGenericPointcutAdvisor;
import org.springframework.util.ClassUtils;

abstract class AbstractModelWrapper implements IModelWrapper {

    private static Log log = LogFactory.getLog(AbstractModelWrapper.class);

    private List<String> unproxyableClasses = defaultUnproxyableClasses();

    private List<String> unsupportedInterfaces = defaultUnsupportedInterfaces();

    public AbstractModelWrapper() {
        super();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T wrapModel(T model, List<IModelChangeListener> modelChangeListeners) {
        log.debug("[" + getClass().getName() + "#wrapModel] model:'" + model + "'");
        try {
            Class<?>[] interfaces;
            if (isUnproxyable(model)) {
                interfaces = new Class[0];
            } else {
                interfaces = ClassUtils.getAllInterfaces(model);
                interfaces = removeUnsupportedInterfaces(interfaces);
            }
            ProxyFactory pf = new ProxyFactory(interfaces);
            pf.setTarget(model);
            AbstractGenericPointcutAdvisor advisor = createGenericPointcutAdvisor();
            Advice aspect = createAdvice(modelChangeListeners);
            advisor.setAdvice(aspect);
            pf.addAdvisor(advisor);
            return (T) pf.getProxy();
        } catch (Throwable e) {
            if (log.isDebugEnabled()) {
                log.warn("[" + getClass().getName() + "#wrapModel] Could not wrap '" + model + "'. " + e.getMessage(), e);
            } else {
                log.warn("[" + getClass().getName() + "#wrapModel] Could not wrap '" + model + "'. " + e.getMessage());
            }
            return model;
        }
    }

    private boolean isUnproxyable(Object obj) {
        boolean re = false;
        for (String className : this.unproxyableClasses) {
            try {
                Class<?> clazz = Class.forName(className);
                if (ClassUtils.isAssignable(clazz, obj.getClass())) {
                    re = true;
                    break;
                }
            } catch (ClassNotFoundException e) {
                log.warn("[" + getClass().getName() + "#isUnproxyable] The class '" + className
                        + "' from the unproxyable classes cannot be found");
            }
        }
        return re;
    }

    private Class<?>[] removeUnsupportedInterfaces(Class<?>[] interfaces) {
        Class<?>[] re = interfaces;
        if (this.unsupportedInterfaces != null) {
            ArrayList<Class<?>> relist = new ArrayList<Class<?>>();
            List<Class<?>> ilist = Arrays.asList(interfaces);
            for (Class<?> interf : ilist) {
                if (!this.unsupportedInterfaces.contains(interf.getName())) {
                    relist.add(interf);
                }
            }
            re = relist.toArray(new Class<?>[0]);
        }
        return re;
    }

    protected abstract AbstractGenericPointcutAdvisor createGenericPointcutAdvisor();

    protected abstract Advice createAdvice(List<IModelChangeListener> modelChangeListeners);

    private List<String> defaultUnproxyableClasses() {
        ArrayList<String> re = new ArrayList<String>();
        re.add(JFrame.class.getName());
        return re;
    }

    private List<String> defaultUnsupportedInterfaces() {
        ArrayList<String> re = new ArrayList<String>();
        re.add(Serializable.class.getName());
        return re;
    }

    public List<String> getUnsupportedInterfaces() {
        return unsupportedInterfaces;
    }

    public void setUnsupportedInterfaces(List<String> unsupportedInterfaces) {
        this.unsupportedInterfaces = unsupportedInterfaces;
    }

}
